package com.zh.mycommon.util.crypto;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;


/**
 * 对称加密
 * <p>
 * 扩展知识：
 * #AES密钥算法(对称加密): 常用的对称加密有AES、DES、3DES、RC2等
 * 算法名称/加密模式/填充方式。
 * DES共有四种工作模式-->>
 * - ECB（不推荐）:电子密码本模式
 * - CBC:加密分组链接模式
 * - CFB:加密反馈模式
 * - OFB:输出反馈模式
 * 在Java进行DES、3DES和AES三种对称加密算法时，常采用的是NoPadding（不填充）、Zeros填充（0填充）、PKCS5Padding填充。
 * 不同的工作模式下，初始化Cipher的代码不一样
 */
public class AesEncryptService {
    // AES密钥算法(对称加密)。常用的对称加密有AES、DES、3DES、RC2等
    private static final String KEY_ALGORITHM = "AES";
    //不同的工作模式下，初始化Cipher的代码不一样
    //算法名称/加密模式/填充方式
    private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";

    private final String key;

    public AesEncryptService(String key) {
        this.key = key;
    }

    /*
        test
     */
    public static void main(String[] args) throws Exception {
        System.out.println("hello world");

        String key = generateAESKey();
        System.out.println("key = " + key);

        AesEncryptService service = new AesEncryptService(key);

        for (int i = 0; i < 3; i++) {
            System.out.println("--------------------------------------");

            String encryString = service.encrypt("hello world");
            System.out.println("encryString = " + encryString);

            String plainString = service.decrypt(encryString);
            System.out.println("plainString = " + plainString);
        }
    }

    /**
     * 加密
     *
     * @param text 明文文本，如果是空文本，则返回空字符串
     * @return 加密后的文本
     */
    public String encrypt(String text) {
        if (StringUtils.isEmpty(text)) return "";

        try {
            return encryptAES(text, key);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 解密
     *
     * @param value 密文 如果是空文本，则返回空字符串
     * @return 解密后的文本
     */
    public String decrypt(String value) {
        if (StringUtils.isEmpty(value)) return "";

        try {
            return decryptAES(value, key);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 加密
     *
     * @param data
     * @param key
     * @return
     * @throws Exception
     */
    private static String encryptAES(String data, String key) throws Exception {
        byte[] iv = new byte[16]; // For AES block size
        SECURE_RANDOM.nextBytes(iv);

        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.getDecoder().decode(key), KEY_ALGORITHM);

        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivSpec);

        byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
        byte[] combine = new byte[iv.length + encrypted.length];
        ArrayUtils.arraycopy(iv, 0, combine, 0, iv.length);
        ArrayUtils.arraycopy(encrypted, 0, combine, iv.length, encrypted.length);

        return Base64.getEncoder().encodeToString(combine);
    }
    private static final SecureRandom SECURE_RANDOM = new SecureRandom(); // 复用SecureRandom实例

    /**
     * 解密
     *
     * @param data
     * @param key
     * @return
     * @throws Exception
     */
    private static String decryptAES(String data, String key) throws Exception {
        byte[] combine = Base64.getDecoder().decode(data);
        byte[] iv = ArrayUtils.subarray(combine, 0, 16);
        byte[] encrypted = ArrayUtils.subarray(combine, 16, combine.length);

        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.getDecoder().decode(key), KEY_ALGORITHM);

        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivSpec);

        byte[] decrypted = cipher.doFinal(encrypted);
        return new String(decrypted, StandardCharsets.UTF_8);
    }

    /**
     * 生成密钥
     * 对称加密密钥
     *
     * @return 密钥
     * @throws NoSuchAlgorithmException 异常
     */
    public static String generateAESKey() throws NoSuchAlgorithmException {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
        keyGenerator.init(256, new SecureRandom());

        SecretKey secretKey = keyGenerator.generateKey();
        return Base64.getEncoder().encodeToString(secretKey.getEncoded());
    }
}